use base64::{decode, encode};
use chrono::offset::Utc;
use chrono::DateTime;
use crypto::buffer::{BufferResult, ReadBuffer, WriteBuffer};
use crypto::{aes, blockmodes, buffer, symmetriccipher};

use colored::Colorize;

use std::convert::TryInto;
use std::sync::MutexGuard;
use std::time::SystemTime;

use std::collections::HashMap;
use std::fs;
use std::io::Write;
use std::io::Read;

// AES-256/CBC/Pkcs encryption.
fn encrypt(
    data: &[u8],
    key: &[u8],
    iv: &[u8],
) -> Result<Vec<u8>, symmetriccipher::SymmetricCipherError> {
    // type available for the platform.
    let mut encryptor =
        aes::cbc_encryptor(aes::KeySize::KeySize256, key, iv, blockmodes::PkcsPadding);

    // read from or written to them.
    let mut final_result = Vec::<u8>::new();
    let mut read_buffer = buffer::RefReadBuffer::new(data);
    let mut buffer = [0; 4096];
    let mut write_buffer = buffer::RefWriteBuffer::new(&mut buffer);

    loop {
        let result = encryptor.encrypt(&mut read_buffer, &mut write_buffer, true)?;

        final_result.extend(
            write_buffer
                .take_read_buffer()
                .take_remaining()
                .iter()
                .map(|&i| i),
        );

        match result {
            BufferResult::BufferUnderflow => break,
            BufferResult::BufferOverflow => {}
        }
    }

    Ok(final_result)
}

// AES-256/CBC/Pkcs encryption.
fn decrypt(
    encrypted_data: &[u8],
    key: &[u8],
    iv: &[u8],
) -> Result<Vec<u8>, symmetriccipher::SymmetricCipherError> {
    let mut decryptor =
        aes::cbc_decryptor(aes::KeySize::KeySize256, key, iv, blockmodes::PkcsPadding);

    let mut final_result = Vec::<u8>::new();
    let mut read_buffer = buffer::RefReadBuffer::new(encrypted_data);
    let mut buffer = [0; 4096];
    let mut write_buffer = buffer::RefWriteBuffer::new(&mut buffer);

    loop {
        let result = decryptor.decrypt(&mut read_buffer, &mut write_buffer, true)?;
        final_result.extend(
            write_buffer
                .take_read_buffer()
                .take_remaining()
                .iter()
                .map(|&i| i),
        );
        match result {
            BufferResult::BufferUnderflow => break,
            BufferResult::BufferOverflow => {}
        }
    }

    Ok(final_result)
}

#[allow(unused)]
fn from_slice(bytes: &[u8]) -> [u8; 32] {
    let mut array = [0; 32];
    let bytes = &bytes[..array.len()]; // panics if not enough data
    array.copy_from_slice(bytes);
    array
}

fn get_iv_by_time() -> [u8; 16] {
    let system_time = SystemTime::now();
    let datetime: DateTime<Utc> = system_time.into();
    let a = &format!("{}", datetime.format("%Y-%m-%d-%H-%M"))[..16];
    let mut b = [0; 16];
    b.copy_from_slice(a.as_bytes());
    b
}

pub struct Encryptor {
    key: [u8; 32],
}

pub trait Cryptor {
    // add code here
    fn encrypt(&self, data: &[u8]) -> Vec<u8>;
    fn decrypt(&self, date: &[u8]) -> Vec<u8>;
    fn en_b64(&self, data: &[u8]) -> String;
    fn de_b64(&self, b64str: &str) -> Vec<u8>;
}

impl Encryptor {
    fn iv(&self) -> [u8; 16] {
        get_iv_by_time()
    }
    #[allow(unused)]
    pub fn new(key: &str) -> Encryptor {
        // let iv: [u8; 16] = [0; 16];

        let ikey: [u8; 32];

        let mut okey: Vec<u8> = key.to_string().into_bytes();
        if key.len() < 32 {
            let mut o: Vec<u8> = (0..(32 - key.len()) as u8).collect();
            okey.append(&mut o);
            ikey = from_slice(okey.as_slice());
        } else {
            ikey = key
                .as_bytes()
                .try_into()
                .expect("slice with incorrect length");
        };
        Encryptor { key: ikey }
    }
    // add code here
}
trait B64 {
    // add code here
    fn to64(&self) -> String;
    fn from64(&self) -> Vec<u8>;
}

impl B64 for String {
    fn to64(&self) -> String {
        encode(self.as_bytes())
    }

    fn from64(&self) -> Vec<u8> {
        decode(self.as_bytes()).expect("not b64 str")
    }
    // add code here
}

impl Cryptor for MutexGuard<'_, Encryptor> {
    fn encrypt(&self, data: &[u8]) -> Vec<u8> {
        let encrypted_data = encrypt(data, &self.key, &self.iv()).ok().unwrap();
        encrypted_data
    }

    fn decrypt(&self, data: &[u8]) -> Vec<u8> {
        match decrypt(data, &self.key, &self.iv()) {
            Ok(s) => s,
            _ => vec![],
        }
    }

    fn en_b64(&self, data: &[u8]) -> String {
        // let data_vec = decode(b64str.as_bytes()).expect("not b64 str");
        let enc_data = self.encrypt(data);
        encode(&enc_data)
    }

    fn de_b64(&self, b64str: &str) -> Vec<u8> {
        let data_vec = decode(b64str.as_bytes()).expect("not b64 str");
        self.decrypt(&data_vec)
    }
}

impl Cryptor for Encryptor {
    // add code here
    fn encrypt(&self, data: &[u8]) -> Vec<u8> {
        let encrypted_data = encrypt(data, &self.key, &self.iv()).ok().unwrap();
        encrypted_data
    }

    fn decrypt(&self, data: &[u8]) -> Vec<u8> {
        match decrypt(data, &self.key, &self.iv()) {
            Ok(s) => s,
            _ => vec![],
        }
    }

    fn en_b64(&self, data: &[u8]) -> String {
        // let data_vec = decode(b64str.as_bytes()).expect("not b64 str");
        let enc_data = self.encrypt(data);
        encode(&enc_data)
    }

    fn de_b64(&self, b64str: &str) -> Vec<u8> {
        let data_vec = decode(b64str.as_bytes()).expect("not b64 str");
        self.decrypt(&data_vec)
    }
}

pub struct AccountS{
    pwd:String,
    data: HashMap<String, String>
}
impl AccountS{
    #[allow(unused)]
    pub fn new(pwd:&str) -> AccountS{
        let home = dirs::home_dir().unwrap();
        let p = home.join(".config").join("scripts").join("account.encrypt");
        let e = Encryptor::new(pwd);
        if p.exists(){
            let mut buf :Vec<u8> = Vec::new();
            if let Ok(mut f) = fs::File::open(&p){
                let _ = f.read_to_end(&mut buf);
                let de_buf = e.decrypt(&buf);
                if let Ok(s) = std::str::from_utf8(&de_buf){
                    let mut j:HashMap<String,String> = HashMap::new();
                    j = match serde_json::from_str(s){
                        Ok(a) => a,
                        Err(e) => { 
                            println!("{}", "error passwd".red().bold());
                            std::process::exit(0);
                        }
                    };
                    return Self{
                       data:j,
                       pwd:pwd.to_string()
                    };
                }
            }
            Self{
                data:HashMap::new(),
                pwd:pwd.to_string()
            }
        }else{
            if !p.parent().unwrap().exists(){
                std::fs::create_dir(p.parent().unwrap());
            }
            Self{
                data:HashMap::new(),
                pwd:pwd.to_string()
            }
        }
    }


    #[allow(unused)]
    pub fn save(&self) -> i32{
        let home = dirs::home_dir().unwrap();
        let p = home.join(".config").join("scripts").join("account.encrypt");
        let e = Encryptor::new(&self.pwd);
        let json = serde_json::json!(self.data.clone());
        let mut en_json = e.encrypt(json.to_string().as_bytes());
        if let Ok(mut f) = fs::File::create(p){
            let _ = f.write_all(&mut en_json);
            return 0;
        }
        -1
    }
    #[allow(unused)]
    pub fn insert_file(&mut self, path:&str) -> i32{
        let p = std::path::Path::new(path);
        if p.exists(){
            if let Ok(mut f) = fs::File::open(&p){
                let mut buf:Vec<u8> = Vec::new();
                let _ = f.read_to_end(&mut buf);
                let p = fs::canonicalize(p).unwrap();
                self.data.insert(format!("[{}] {}","F",p.to_str().unwrap()), encode(&buf));
            };
            0
        }else{
            -1
        }
    }

    #[allow(unused)]
    pub fn insert(&mut self, n:&str, v:&str) -> i32{
        self.data.insert(format!("[{}] {}","A",n), v.to_string());
        0
    }

    #[allow(unused)]
    pub fn insert_json<F>(&mut self, n:&str, v:&F) -> i32
    where F: serde::Serialize
    {
        let vv = serde_json::json!(v);
        self.data.insert(format!("[{}] {}","J",n), vv.to_string());
        0
    }

    #[allow(unused)]
    pub fn ls(&self) -> Vec<String>{
        let mut output: Vec<String> = Vec::new();
        self.data.iter().map(|(k,v)| {
                output.push(k.to_string());
        }).collect::<Vec<_>>();
        output
    }

    #[allow(unused)]
    pub fn search(&self, key:&str) -> Vec<String>{
        let mut output: Vec<String> = Vec::new();
        self.data.iter().map(|(k,v)| {
            if k.contains(key){
                output.push(k.to_string())
            }
        }).collect::<Vec<_>>();
        output
    }

    pub fn get(&self, key:&str)-> Option<String>{
        if self.data.contains_key(key){
            Some(self.data.get(key).unwrap().clone())
        }else{
            None
        }
    }
    pub fn change_pwd(&mut self ,pwd:&str){
        self.pwd= pwd.to_string();
        self.save();
    }

    // pub fn borrow_mut(&self) -> &mut Self{
    //     let s = self.to_owned();
    //     s
    // }

}
